﻿// <copyright file="TrackMultiScaleImage.cs" company="Microsoft">
// Copyright (c) 2010 Microsoft All Rights Reserved
// </copyright>
// <author>Microsoft</author>
// <email>mischero@microsoft.com</email>
// <date>2010-01-01</date>
// <summary>Web analytics service behavior class definition</summary>

namespace Microsoft.WebAnalytics.Media
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    using System.Windows.Markup;
    using System.Xml.Linq;
    using Microsoft.LiveLabs.DeepZoom;
    using Microsoft.WebAnalytics;

    /// <summary>
    /// <img src="../media/Microsoft.WebAnalytics.Media.TrackMultiScaleImage.24x24.png"/> 
    /// Track Deep Zoom Events 
    /// </summary>
    /// <remarks>When the MultiScaleImage.ViewPortChanged event fires, look 
    /// into the Locations collection and send a LocationNear event if the 
    /// centerpoint and zoom level are near a location.
    /// When used with a Deep Zoom Collection, if a image with a Tag property is zoomed
    /// to and the image fills the screen, then a SubImageInView event fires with the Tag in the AnalyticsEvent.ActionValue</remarks>
    [ContentProperty("Locations")]
    public class TrackMultiScaleImage : Behavior<MultiScaleImage>
    {
        /// <summary>
        /// The metadata tags
        /// </summary>
        private IEnumerable<string> metadataTags;

        /// <summary>
        /// The current metadata tag
        /// </summary>
        private string currentTag;

        /// <summary>
        /// Initializes a new instance of the TrackMultiScaleImage class.
        /// </summary>
        public TrackMultiScaleImage()
        {
            this.Locations = new ObservableCollection<MultiScaleImageLocation>();
        }

        #region Properties

        /// <summary>
        /// Gets or sets the locations to track
        /// </summary>
        public ObservableCollection<MultiScaleImageLocation> Locations { get; set; }

        /// <summary>
        /// Gets or sets the event Logging method
        /// </summary>
        [Import("Log")]
        public Action<AnalyticsEvent> Log { get; set; }

        /// <summary>
        /// Gets or sets the Metadata URL created by the Deep Zoom Composer
        /// </summary>
        [TypeConverter(typeof(UriTypeConverter))]
        public Uri Metadata { get; set; }
        #endregion

        #region Methods
        /// <summary>
        /// Attach events and initialize parts
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            CompositionInitializerEx.SatisfyImports(this);

            this.AssociatedObject.ViewportChanged += new RoutedEventHandler(this.OnViewportChanged);

            this.AssociatedObject.Loaded += new RoutedEventHandler(this.AssociatedObject_Loaded);

            this.AssociatedObject.Unloaded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs args)
            {
                System.Windows.Interactivity.Interaction.GetBehaviors(this.AssociatedObject).Remove(this);
            });
        }

        /// <summary>
        /// Detach events
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.ViewportChanged -= new RoutedEventHandler(this.OnViewportChanged);
        }

        #endregion

        #region Implementation
        /// <summary>
        /// Use the Pythagorean Theorum to get the distance between two points
        /// </summary>
        /// <param name="p1">the first point</param>
        /// <param name="p2">the second point</param>
        /// <returns>the distance between the two points</returns>
        private static double Distance(Point p1, Point p2)
        {
            return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
        }

        /// <summary>
        /// Load the metadata file when the control loads
        /// </summary>
        /// <param name="sender">the MultiScaleImage</param>
        /// <param name="e">the routed event arguments</param>
        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            if (this.Metadata == null)
            {
                return;
            }

            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            var client = new WebClient();

            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnDownloadStringCompleted);

            client.DownloadStringAsync(this.Metadata);
        }

        /// <summary>
        /// Metadata has been downloaded
        /// </summary>
        /// <param name="sender">the WebClient</param>
        /// <param name="e">the downloaded string completed event arguments</param>
        private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine(e.Error.Message);

                return;
            }

            var doc = XDocument.Parse(e.Result);

            this.metadataTags = from image in doc.Root.Elements("Image")
                                select image.Element("Tag").Value;
        }

        /// <summary>
        /// Viewport changed event handler
        /// </summary>
        /// <param name="sender">the multiscale image</param>
        /// <param name="e">the routed event argument</param>
        private void OnViewportChanged(object sender, RoutedEventArgs e)
        {
            var center = new Point(
                this.AssociatedObject.ViewportOrigin.X + 0.5,
                this.AssociatedObject.ViewportOrigin.Y + 0.5);

            var hitLocations = from location in this.Locations
                               where this.AssociatedObject.ViewportWidth <= location.ViewportWidth &&
                                    Distance(center, new Point(location.CenterX, location.CenterY)) <= location.CenterTolerance
                               select new AnalyticsEvent()
                               {
                                   EventArgs    = location.CustomProperties,
                                   Name         = "LocationNear",
                                   ObjectName   = this.AssociatedObject.Name,
                                   ObjectType   = this.AssociatedObject.GetType().Name,
                                   ActionValue  = location.Name,
                               };

            if (hitLocations.Count() == 0)
            {
                ////System.Diagnostics.Debug.WriteLine("Center: {0}", center);

                foreach (var location in this.Locations)
                {
                    ////System.Diagnostics.Debug.WriteLine("Distance from {0}: {1}", location.Name, Distance(center, new Point(location.CenterX, location.CenterY)));
                }
            }
            else
            {
                foreach (var logEvent in hitLocations)
                {
                    this.Log(logEvent);
                }
            }

            if (this.metadataTags != null && this.metadataTags.Count() == this.AssociatedObject.SubImages.Count)
            {
                for (var i = 0; i < this.AssociatedObject.SubImages.Count; i++)
                {
                    var subImage = this.AssociatedObject.SubImages[i];

                    if (this.AssociatedObject.IsSubImageFillingView(subImage))
                    {
                        if (this.currentTag != this.metadataTags.ElementAt(i))
                        {
                            this.currentTag = this.metadataTags.ElementAt(i);

                            if (string.IsNullOrEmpty(this.currentTag))
                            {
                                continue;
                            }

                            ////System.Diagnostics.Debug.WriteLine(this.currentTag);

                            var analyticsEvent = new AnalyticsEvent()
                               {
                                   Name         = "SubImageInView",
                                   ObjectName   = this.AssociatedObject.Name,
                                   ObjectType   = this.AssociatedObject.GetType().Name,
                                   ActionValue  = this.currentTag,
                                   Value        = i,
                               };

                            this.Log(analyticsEvent);
                        }
                    }
                }
            }
        }

        #endregion
    }
}
